﻿using MicroDb.Querys;

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace MicroDb.Mysql
{
    class MysqlResolveQuery : MysqlResolveBase
    {
        /// <summary>
        /// 返回值
        /// </summary>
        private Type ReturnType;


        private string SelectFirst = "";

        /// <summary>
        /// 单项值
        /// </summary>
        private bool IsScalar;

        private bool IsAverage;

        private LambdaExpression Select;

        private string SelectFormat = "{0}";

        private bool IsFirst;


        private List<string> Wheres = new List<string>();

        private List<string> Orders = new List<string>();

        private List<string> Groups = new List<string>();

        private List<string> Thens = new List<string>();

        private string JoinStr;

        private int Take;

        private int Skip;


        public MysqlResolveQuery(MethodCallExpression callExpression, SqlExecute sqlExecute, Dictionary<string, string> baseTabs = null, MysqlResolveBase parent = null) : base(sqlExecute)
        {
            base.BaseTabs = baseTabs;
            base.Parent = parent;
            this.Init(callExpression);
        }

        public void Init(MethodCallExpression callExpression)
        {
            while (true)
            {
                switch (callExpression.Method.Name)
                {
                    case "Any":
                        #region Any
                        SelectFormat = "1";
                        ReturnType = typeof(bool);
                        IsScalar = true;
                        if (callExpression.Arguments.Count == 2)
                            Wheres.Add(WhereAny(callExpression.Arguments[1] as UnaryExpression));

                        var source = GetValue(callExpression.Arguments[0]);
                        //如果是IQueryable 执行子查询
                        if (source is IQueryable)
                        {
                            callExpression = (source as IQueryable).Expression as MethodCallExpression;
                            continue;
                        }
                        else
                        {
                            throw new NotImplementedException("Any");
                        }
                    #endregion
                    case "SuffixAutoCreate":
                        Suffix = GetValue(callExpression.Arguments[1]).ToString();
                        AutoCreate = true;
                        break;
                    case "Suffix":
                        Suffix = GetValue(callExpression.Arguments[1]).ToString();
                        break;
                    case "SelectAggregate":
                        IsFirst = true;
                        IsAverage = true;
                        SelectAggregate(callExpression.Arguments[1] as UnaryExpression);
                        break;
                    case "Select":

                        if (NewTab(callExpression))
                        {
                            if (ReturnType == null)
                                ReturnType = ((callExpression.Arguments.LastOrDefault() as UnaryExpression).Operand as LambdaExpression).ReturnType;
                            return;
                        }
                        if (callExpression.Object != null)
                        {//表链接
                            StartJoin(callExpression.Object as ConstantExpression, callExpression.Arguments[0] as UnaryExpression);
                            return;
                        }
                        ResolveSelect(callExpression.Arguments[1] as UnaryExpression);
                        break;
                    case "Where":
                        Wheres.Add(WhereAny(callExpression.Arguments[1] as UnaryExpression));
                        break;
                    case "WhereCustom":
                        Wheres.Add(ResolveCustomWhere(callExpression.Arguments[1] as UnaryExpression));
                        break;
                    case "ThenBy":
                        Thens.Add(ResolveOrder(callExpression.Arguments[1] as UnaryExpression));
                        break;
                    case "OrderBy":
                        Orders.Add(ResolveOrder(callExpression.Arguments[1] as UnaryExpression));
                        break;
                    case "ThenByDescending":
                        Thens.Add(ResolveOrder(callExpression.Arguments[1] as UnaryExpression) + " DESC");
                        break;
                    case "OrderByDescending":
                        Orders.Add(ResolveOrder(callExpression.Arguments[1] as UnaryExpression) + " DESC");
                        break;
                    case "OrderByRand":
                        Orders.Add("RAND()");
                        break;
                    case "GroupBy":
                        Groups.AddRange(ResolveGroup(callExpression.Arguments[1] as UnaryExpression));
                        break;
                    //聚合查询
                    case "Count":
                    case "LongCount":
                        SelectFormat = "COUNT(1)";
                        IsScalar = true;
                        IsAverage = true;
                        ReturnType = typeof(int);
                        if (callExpression.Arguments.Count == 2)
                            Wheres.Add(WhereAny(callExpression.Arguments[1] as UnaryExpression));
                        break;
                    case "Max":
                    case "Min":
                    case "Sum":
                    case "Average":
                        IsAverage = true;
                        IsScalar = true;
                        string funName = callExpression.Method.Name.ToUpper();
                        if (funName == "AVERAGE") funName = "AVG";
                        SelectFormat = "IFNULL(" + funName + "({0}),0)";
                        if (callExpression.Arguments.Count == 2)
                        {
                            if (NewTab(callExpression))
                            {
                                ReturnType = ((callExpression.Arguments[1] as UnaryExpression).Operand as LambdaExpression).ReturnType;
                                return;
                            }
                            ResolveSelect(callExpression.Arguments[1] as UnaryExpression);
                        }
                        break;
                    case "Distinct":
                        SelectFirst = "DISTINCT";
                        break;
                    case "First":
                    case "FirstOrDefault":
                        Take = 1;
                        IsFirst = true;
                        if (callExpression.Arguments.Count == 2)
                            Wheres.Add(WhereAny(callExpression.Arguments[1] as UnaryExpression));
                        break;
                    case "Take":
                        Take = (int)GetValue(callExpression.Arguments[1]);
                        break;
                    case "Skip":
                        Skip = (int)GetValue(callExpression.Arguments[1]);
                        break;

                    default:
                        throw new NotImplementedException("不支持的方法：" + callExpression.Method.Name);
                }
                if (callExpression.Arguments[0].NodeType == ExpressionType.Call)
                {
                    callExpression = callExpression.Arguments[0] as MethodCallExpression;
                }
                else
                {
                    if (callExpression.Arguments[0].NodeType == ExpressionType.Constant)
                    {
                        base.TableType = (GetValue(callExpression.Arguments[0]) as IQueryable).ElementType;
                    }
                    break;
                }
            }
        }

        private IEnumerable<string> ResolveGroup(UnaryExpression unaryExpression)
        {
            var lambda = (unaryExpression.Operand as LambdaExpression);
            var parameters = GetDefaultParameters(lambda);
            switch (lambda.Body.NodeType)
            {
                case ExpressionType.New:
                    NewExpression newExpression = lambda.Body as NewExpression;
                    for (int i = 0; i < newExpression.Members.Count; i++)
                    {
                        yield return ResolveGroup(newExpression.Members[i], newExpression.Arguments[i], parameters);
                    }
                    yield break;
                case ExpressionType.MemberInit:
                    MemberInitExpression memberInit = lambda.Body as MemberInitExpression;
                    foreach (MemberAssignment item in memberInit.Bindings)
                    {
                        yield return ResolveGroup(item.Member, item.Expression, parameters);
                    }
                    yield break;
                case ExpressionType.MemberAccess:
                    var member = lambda.Body as MemberExpression;
                    yield return CostName(member.Member.Name);
                    break;
                default:
                    throw new Exception("无效的方法：" + unaryExpression.ToString());
            }
        }

        //private string GetTabName(Type tableType)
        //{
        //    DefaultPropertyAttribute defaultProperty = TableType.GetCustomAttribute<DefaultPropertyAttribute>();
        //    if (defaultProperty != null)
        //    {
        //        return CostName(defaultProperty.Name);
        //    }
        //    return CostName(TableType.Name);
        //}

        private void StartJoin(ConstantExpression constantExpression, UnaryExpression unaryExpression)
        {
            var dbjon = constantExpression.Value as IDBJoinQueryable;

            JoinTableObject joinTable = dbjon.Do();

            rept = new Dictionary<string, Type>();
            rept.Add("t0", joinTable.Source.ElementType);
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.Append(CreateResolve(joinTable.Source.Expression, this, sqlExecute: sqlExecute).BuildSql(true) + " AS t0");
            var i = 1;
            foreach (var item in joinTable.Joins)
            {
                //if (rept.ContainsKey(item.Source.ElementType))
                //{
                //    throw new Exception("表链接重复");
                //}
                rept.Add("t" + i, item.Source.ElementType);
                stringBuilder.AppendFormat(" {0} JOIN {1} AS {2}", item.JoinType.ToString(), CreateResolve(item.Source.Expression, this, sqlExecute: sqlExecute).BuildSql(true), "t" + i);
                if (item.OnPredicate != null)
                {
                    stringBuilder.AppendFormat(" ON {0}", WhereAny(item.OnPredicate as LambdaExpression));
                }
                i++;
            }
            JoinStr = stringBuilder.ToString();
            if (joinTable.WherePredicates != null)
            {
                foreach (LambdaExpression lambda in joinTable.WherePredicates)
                {
                    Wheres.Add(string.Concat("(", WhereAny(lambda), ")"));
                }
            }
            if (joinTable.OrderByPredicates != null)
            {

                foreach (var (expression, type) in joinTable.OrderByPredicates)
                {
                    if (type != OrderType.None)
                    {
                        var lambda = expression as LambdaExpression;
                        Orders.Add(ResolveAny(lambda.Body, GetDefaultParameters(lambda)) + " " + (type.ToString().ToUpper()));
                    }
                }
            }

            ResolveSelect(unaryExpression);
        }



        private string TableName;

        public override string GetTabName()
        {
            if (TableName != null) return TableName;
            return base.GetTabName();
        }
        public string ResolveOrder(UnaryExpression unaryExpression)
        {
            var lambda = (unaryExpression.Operand as LambdaExpression);
            return ResolveAny(lambda.Body, GetDefaultParameters(lambda));
        }


        private bool NewTab(MethodCallExpression callExpression)
        {
            //还可以简化
            if (Select == null && Wheres.Count == 0 && Orders.Count == 0)
            {
                return false;
            }
            var resolve = CreateResolve(callExpression, this, sqlExecute: sqlExecute);


            if (!string.IsNullOrEmpty(SelectFirst) && resolve is MysqlResolveQuery mysqlResolve)
            {
                mysqlResolve.SelectFirst = SelectFirst;
                SelectFirst = "";
            }
            //下潜分支
            if (!string.IsNullOrEmpty(Abridge))
            {
                TableName = string.Concat("(", resolve.BuildSql(), ")");
            }
            else
            {
                TableName = string.Concat("(", resolve.BuildSql(), ") AS TB");
            }
            return true;
        }

        private void SelectAggregate(UnaryExpression unaryExpression)
        {
            var lambda = unaryExpression.Operand as LambdaExpression;
            ReturnType = lambda.ReturnType;
            Select = lambda;
        }


        public void ResolveSelect(UnaryExpression unaryExpression)
        {
            var lambda = unaryExpression.Operand as LambdaExpression;
            ReturnType = lambda.ReturnType;
            Select = lambda;
        }

        private string GetWhereSql()
        {
            if (Wheres.Count == 0)
            {
                return "";
            }
            return string.Concat("WHERE ", string.Join(" AND ", Wheres));
        }

        protected string ResolveGroupSelect(MemberInfo member, Expression expression, Dictionary<string, string> pairs)
        {
            string name = CostName(member.Name);
            if (expression.NodeType == ExpressionType.MemberAccess)
            {
                var mem = expression as MemberExpression;
                if (mem.Member.Name == "Key")
                {
                    if (Groups.Count == 1)
                    {
                        return $"{Groups[0]} AS {name}";
                    }
                    throw Error.SystemError("包含多个分组字段");
                }
            }
            if (expression.NodeType == ExpressionType.Call)
            {
                return $"{ResolveSelectFunc(expression as MethodCallExpression, pairs)} AS {name}";
            }
            return $"{ResolveAny(expression, pairs)} AS {name}"; ;
        }



        private string GetGroupSelect()
        {
            var parameters = GetDefaultParameters(Select);
            List<string> selects = new List<string>();
            switch (Select.Body.NodeType)
            {
                case ExpressionType.New:
                    NewExpression newExpression = Select.Body as NewExpression;
                    for (int i = 0; i < newExpression.Members.Count; i++)
                    {
                        selects.Add(ResolveGroupSelect(newExpression.Members[i], newExpression.Arguments[i], parameters));
                    }
                    break;
                case ExpressionType.MemberInit:
                    MemberInitExpression memberInit = Select.Body as MemberInitExpression;
                    foreach (MemberAssignment item in memberInit.Bindings)
                    {
                        selects.Add(ResolveGroupSelect(item.Member, item.Expression, parameters));
                    }
                    break;
                case ExpressionType.MemberAccess:
                    var member = Select.Body as MemberExpression;
                    selects.Add(CostName(member.Member.Name));
                    break;
                case ExpressionType.Call:
                    selects.Add(ResolveAny(Select.Body, parameters));
                    break;
                case ExpressionType.Parameter:
                    selects.Add(parameters[Select.Body.ToString()] + ".*");
                    break;
                default:
                    selects.Add(ResolveAny(Select.Body, parameters));
                    break;
            }
            if (selects.Count == 0) return "*";
            return string.Join(",", selects);
        }
        private string GetSelect()
        {
            if (Select == null) return "*";
            if (Groups.Count > 0) return GetGroupSelect();
            var parameters = GetDefaultParameters(Select);
            List<string> selects = new List<string>();
            switch (Select.Body.NodeType)
            {
                case ExpressionType.New:
                    NewExpression newExpression = Select.Body as NewExpression;
                    if (newExpression.Members == null)
                    {
                        if (newExpression.Arguments.Count == 0) break;
                        for (int i = 0; i < newExpression.Arguments.Count; i++)
                        {
                            selects.Add(ResolveSelect(string.Empty, newExpression.Arguments[i], parameters));
                        }
                    }
                    else
                        for (int i = 0; i < newExpression.Members.Count; i++)
                        {
                            selects.Add(ResolveSelect(newExpression.Members[i], newExpression.Arguments[i], parameters));
                        }
                    break;
                case ExpressionType.MemberInit:
                    MemberInitExpression memberInit = Select.Body as MemberInitExpression;
                    foreach (MemberAssignment item in memberInit.Bindings)
                    {
                        selects.Add(ResolveSelect(item.Member, item.Expression, parameters));
                    }
                    break;
                case ExpressionType.MemberAccess:
                    var member = Select.Body as MemberExpression;
                    selects.Add(CostName(member.Member.Name));
                    break;
                case ExpressionType.Call:
                    selects.Add(ResolveAny(Select.Body, parameters));
                    break;
                case ExpressionType.Parameter:
                    selects.Add(parameters[Select.Body.ToString()] + ".*");
                    break;
                default:
                    selects.Add(ResolveAny(Select.Body, parameters));
                    break;
            }
            if (selects.Count == 0) return "*";
            return string.Join(",", selects);
        }

        private string GetGroup()
        {
            if (Groups.Count == 0) return "";
            return " GROUP BY " + string.Join(",", Groups);
        }
        private string MyBuildSql()
        {
            if (IsAverage && (Take > 0 || Skip > 0))
            {
                if (!string.IsNullOrEmpty(JoinStr))
                {
                    return $"SELECT {SelectFirst} {string.Format(SelectFormat, GetSelect()) } FROM (SELECT * FROM {JoinStr} {GetWhereSql()} {GetOrderSql()} {GetLimitSql()}) AS TS";
                }
                return $"SELECT {SelectFirst} {string.Format(SelectFormat, GetSelect()) } FROM (SELECT * FROM {GetTabName()} {(string.IsNullOrEmpty(Abridge) ? "" : ("AS " + Abridge + " "))}{GetWhereSql()}{GetGroup()} {GetOrderSql()} {GetLimitSql()}) AS TS";
            }
            if (!string.IsNullOrEmpty(JoinStr))
            {
                return $"SELECT {SelectFirst} {string.Format(SelectFormat, GetSelect()) } FROM {JoinStr} {GetWhereSql()} {GetOrderSql()} {GetLimitSql()}";
            }
            return $"SELECT {SelectFirst} {string.Format(SelectFormat, GetSelect()) } FROM {GetTabName()} {(string.IsNullOrEmpty(Abridge) ? "" : ("AS " + Abridge + " "))}{GetWhereSql()}{GetGroup()} {GetOrderSql()} {GetLimitSql()}";
        }

        public override string BuildSql(bool isEasy = false)
        {
            if (isEasy)
            {
                if (Select == null && Wheres.Count == 0 && Orders.Count == 0)
                {
                    return GetTabName();
                }
                else
                {
                    return $"({MyBuildSql()})";
                }
            }
            else
            {
                return MyBuildSql();
            }
        }

        private string GetOrderSql()
        {
            if (Orders.Count + Thens.Count == 0)
            {
                return "";
            }
            if (SelectFormat == "COUNT(1)")
            {
                return "";
            }
            var order = new List<string>();
            order.AddRange(Orders);
            order.AddRange(Thens);
            return $"ORDER BY {string.Join(",", order) }";
        }

        private string GetLimitSql()
        {
            if (Take > 0)
            {
                return string.Format(" LIMIT {0} ,{1}", Skip, Take);
            }
            return "";
        }


        public override object GetResult()
        {
            string sql = BuildSql();

            return TryCreateTable(() =>
             {
                 if (IsScalar)
                 {
                     return sqlExecute.ExecuteScalar(CommandType.Text, sql, Args);
                 }
                 else
                 {
                     if (IsFirst)
                     {
                         return sqlExecute.ExecuteEntityObject(CommandType.Text, sql, Args, ReturnType ?? base.TableType);
                     }
                     return sqlExecute.ExecuteListObject(CommandType.Text, sql, Args, ReturnType ?? base.TableType);
                 }

             });
        }

    }
}